
// Build a Gamma ramp according to sRGB
var g_GammaRamp = [
  0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,   1,   1,   1,   1,   1, 
  1,   1,   2,   2,   2,   2,   2,   2,   2,   2,   3,   3,   3,   3,   3,   3, 
  4,   4,   4,   4,   4,   5,   5,   5,   5,   6,   6,   6,   6,   7,   7,   7, 
  8,   8,   8,   8,   9,   9,   9,  10,  10,  10,  11,  11,  12,  12,  12,  13, 
 13,  13,  14,  14,  15,  15,  16,  16,  17,  17,  17,  18,  18,  19,  19,  20, 
 20,  21,  22,  22,  23,  23,  24,  24,  25,  25,  26,  27,  27,  28,  29,  29, 
 30,  30,  31,  32,  32,  33,  34,  35,  35,  36,  37,  37,  38,  39,  40,  41, 
 41,  42,  43,  44,  45,  45,  46,  47,  48,  49,  50,  51,  51,  52,  53,  54, 
 55,  56,  57,  58,  59,  60,  61,  62,  63,  64,  65,  66,  67,  68,  69,  70, 
 71,  72,  73,  74,  76,  77,  78,  79,  80,  81,  82,  84,  85,  86,  87,  88, 
 90,  91,  92,  93,  95,  96,  97,  99, 100, 101, 103, 104, 105, 107, 108, 109, 
111, 112, 114, 115, 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133, 
134, 136, 138, 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159, 
161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 
190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 
222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253, 255, 
];

var g_GammaInverse = [
  0,  13,  22,  28,  34,  38,  42,  46,  50,  53,  56,  59,  61,  64,  66,  69, 
 71,  73,  75,  77,  79,  81,  83,  85,  86,  88,  90,  92,  93,  95,  96,  98, 
 99, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119, 
120, 121, 122, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 
137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 148, 149, 150, 151, 
152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, 
165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 175, 176, 
177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 185, 185, 186, 187, 187, 
188, 189, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 196, 196, 197, 197, 
198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206, 206, 207, 
208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, 
216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 
225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 
233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 238, 239, 239, 240, 240, 
241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248, 
248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255, 
];

var _color_sqrt = [
	0x00, 0x03, 0x07, 0x0B, 0x0F, 0x12, 0x16, 0x19, 0x1D, 0x20, 0x23, 0x26, 0x29, 0x2C, 0x2F, 0x32, 
	0x35, 0x37, 0x3A, 0x3C, 0x3F, 0x41, 0x43, 0x46, 0x48, 0x4A, 0x4C, 0x4E, 0x50, 0x52, 0x54, 0x56, 
	0x57, 0x59, 0x5B, 0x5C, 0x5E, 0x60, 0x61, 0x63, 0x64, 0x65, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 
	0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 
	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 
	0x8F, 0x90, 0x91, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 
	0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA8, 
	0xA9, 0xAA, 0xAA, 0xAB, 0xAC, 0xAD, 0xAD, 0xAE, 0xAF, 0xB0, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB4, 
	0xB5, 0xB5, 0xB6, 0xB7, 0xB7, 0xB8, 0xB9, 0xBA, 0xBA, 0xBB, 0xBC, 0xBC, 0xBD, 0xBE, 0xBE, 0xBF, 
	0xC0, 0xC0, 0xC1, 0xC2, 0xC2, 0xC3, 0xC4, 0xC4, 0xC5, 0xC6, 0xC6, 0xC7, 0xC7, 0xC8, 0xC9, 0xC9, 
	0xCA, 0xCB, 0xCB, 0xCC, 0xCC, 0xCD, 0xCE, 0xCE, 0xCF, 0xD0, 0xD0, 0xD1, 0xD1, 0xD2, 0xD3, 0xD3, 
	0xD4, 0xD4, 0xD5, 0xD6, 0xD6, 0xD7, 0xD7, 0xD8, 0xD9, 0xD9, 0xDA, 0xDA, 0xDB, 0xDC, 0xDC, 0xDD, 
	0xDD, 0xDE, 0xDE, 0xDF, 0xE0, 0xE0, 0xE1, 0xE1, 0xE2, 0xE2, 0xE3, 0xE4, 0xE4, 0xE5, 0xE5, 0xE6, 
	0xE6, 0xE7, 0xE7, 0xE8, 0xE9, 0xE9, 0xEA, 0xEA, 0xEB, 0xEB, 0xEC, 0xEC, 0xED, 0xED, 0xEE, 0xEE, 
	0xEF, 0xF0, 0xF0, 0xF1, 0xF1, 0xF2, 0xF2, 0xF3, 0xF3, 0xF4, 0xF4, 0xF5, 0xF5, 0xF6, 0xF6, 0xF7, 
	0xF7, 0xF8, 0xF8, 0xF9, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFC, 0xFC, 0xFD, 0xFD, 0xFE, 0xFE, 0xFF
];



function BmpBlender() {
}


BmpBlender.prototype.BlendColor = function (blend_mode, back_color, src_color) {

switch (blend_mode) {
		case 0:
			return src_color;
		case 1:
			return src_color * back_color / 255;
		case 2:
			return src_color + back_color - src_color * back_color / 255;
		case 3:
			return BmpBlender.prototype.BlendColor(8, src_color, back_color);
		case 4:
			return src_color < back_color ? src_color : back_color;
		case 5:
			return src_color > back_color ? src_color : back_color;
		case 6: {
			if (src_color == 255) return src_color;
			var result = back_color * 255 / (255 - src_color);
			if (result > 255) return 255;
			return result; }
		case 7: {
			if (src_color == 0) return src_color;
			var result = (255 - back_color) * 255 / src_color;
			if (result > 255) result = 255;
			return 255 - result; }
		case 8:
			if (src_color < 128)
				return (src_color * back_color * 2) / 255;
			return BmpBlender.prototype.BlendColor(2, back_color, 2 * src_color - 255);
		case 9: {
			if (src_color < 128)
				return back_color - (255 - 2 * src_color) * back_color * (255 - back_color) / 255 / 255;
			return back_color + (2 * src_color - 255) * (_color_sqrt[back_color] - back_color) / 255; }
		case 10:
			return back_color < src_color ? src_color - back_color : back_color - src_color;
		case 11:
			return back_color + src_color - 2 * back_color * src_color / 255;
	}
	return src_color;
};

BmpBlender.prototype.Lum = function (r,g,b) {
return (r * 30 + g * 59 + b * 11)/100;
};

BmpBlender.prototype.ClipColor = function (r,g,b) {
var l = BmpBlender.prototype.Lum(r,g,b);
	var n = r;
	if (g < n) n = g;
	if (b < n) n = b;
	var x = r;
	if (g > x) x = g;
	if (b > x) x = b;
	if (n < 0) {
		r = l + ((r - l) * l / (l - n));
		g = l + ((g - l) * l / (l - n));
		b = l + ((b - l) * l / (l - n));
	}
	if (x > 255) {
		r = l + ((r - l) * (255 - l) / (x - l));
		g = l + ((g - l) * (255 - l) / (x - l));
		b = l + ((b - l) * (255 - l) / (x - l));
	}
	return [r,g,b];
};

BmpBlender.prototype.SetLum = function (r,g,b,l) {
var d = l - BmpBlender.prototype.Lum(r,g,b);
	r += d;
	g += d;
	b += d;
	return BmpBlender.prototype.ClipColor(r, g, b);
};

BmpBlender.prototype.Sat = function (r,g,b) {
var n = r;
	if (g < n) n = g;
	if (b < n) n = b;
	var x = r;
	if (g > x) x = g;
	if (b > x) x = b;
	return x - n;
};

BmpBlender.prototype.SetSat = function (r,g,b,s) {
var max = r;
var nMax = 0;
var mid = r;
var nMid = 0;
var min = r;
var nMin = 0;

if(g > max)
{
    max = g;
    nMax = 1;
}

if(b > max){
max = b;
nMax=2;
}

if(g < min){
min = g;
nMin = 1;
}
if(b < min){
min = b;
nMin = 2;
}

if(max == min) return[0,0,0];


if(nMax == 0){
if(nMin == 1){mid = b; nMid = 2;}
else {mid = g; nMid = 1;}
}else if(1 == nMax){
if(nMin == 0){mid = b; nMid = 2;}
else {mid = r; nMid = 0;}
}else{
if(nMin == 1){mid = r; nMid = 0;}
else {mid = g; nMid = 1;}
}

if(max > min)
{
    mid = (mid - min) * s / (max - min);
		max = s;
		min = 0;
}
var dr=0; 
var dg=0;
var db=0;
switch(nMax) {
case 0:
    dr = max;
	break;
case 1:
dg = max;
	break;
    case 2:
    db = max;
	break;
default:
break;
}

switch(nMid) {
case 0:
    dr = mid;
	break;
case 1:
dg = mid;
	break;
    case 2:
    db = mid;
	break;
default:
break;
}

switch(nMin) {
case 0:
    dr = min;
	break;
case 1:
dg = min;
	break;
    case 2:
    db = min;
	break;
default:
break;
}
return [dr,dg,db];
};

BmpBlender.prototype.RGBBlend = function (blend_mode, src_pixel, dest_pixel) {
    var src =[0,0,0];
    src[0] = src_pixel[0];
    src[1] = src_pixel[1];
    src[2] = src_pixel[2];
    var back = [0,0,0];
    back[0] = dest_pixel[0];
    back[1] = dest_pixel[1];
    back[2] = dest_pixel[2];

    var result = null;
    switch (blend_mode) {
		case 21:
        {
        var s = BmpBlender.prototype.Sat(back[0], back[1], back[2]);
        var rgb = BmpBlender.prototype.SetSat(src[0], src[1], src[2], s);
        var l = BmpBlender.prototype.Lum(back[0], back[1], back[2]);
        result = BmpBlender.prototype.SetLum(rgb[0], rgb[1], rgb[2], l);
        }
			
			break;
		case 22:
        {
        var s = BmpBlender.prototype.Sat(src[0], src[1], src[2]);
        var rgb = BmpBlender.prototype.SetSat(back[0], back[1], back[2], s);
        var l = BmpBlender.prototype.Lum(back[0], back[1], back[2]);
        result = BmpBlender.prototype.SetLum(rgb[0], rgb[1], rgb[2], l);
        }
		
			break;
		case 23:
        {
        var l = BmpBlender.prototype.Lum(back[0], back[1], back[2]);
        result = BmpBlender.prototype.SetLum(src[0], src[1], src[2], l);
        }
			
			break;
		case 24:
        {
        var l = BmpBlender.prototype.Lum(src[0], src[1], src[2]);
        result = BmpBlender.prototype.SetLum(back[0], back[1], back[2], l);
        }
			break;
	}
    return result;

};

function BmpCompositer() {
}
//return [dest_left, dest_top, width, height, src_left, src_top]
BmpCompositer.prototype.GetOverlapRect = function (dest_left, dest_top, width, height, orgWidth, orgHeight, src_width, src_height, src_left, src_top) {
    if (width == 0 || height == 0) return;
    // get offset
    var x_offset = dest_left - src_left;
    var y_offset = dest_top - src_top;

    // clip the source rect
    var src_right = src_left + width;
    var src_bottom = src_top + height;
    var src_rect = [src_left > src_right ? src_right : src_left, src_top > src_bottom ? src_bottom : src_top, src_left > src_right ? src_left : src_right, src_top > src_bottom ? src_top : src_bottom];
    var src_bound = [0, 0, src_width, src_height];
    if (src_rect[0] < src_bound[0]) src_rect[0] = src_bound[0];
    if (src_rect[1] < src_bound[1]) src_rect[1] = src_bound[1];
    if (src_rect[2] > src_bound[2]) src_rect[2] = src_bound[2];
    if (src_rect[3] > src_bound[3]) src_rect[3] = src_bound[3];

    // clip the dest rect
    var left = src_rect[0] + x_offset;
    var top = src_rect[1] + y_offset;
    var right = src_rect[2] + x_offset;
    var bottom = src_rect[3] + y_offset;
    var dest_rect = [left, top, right, bottom];
    var dest_bound = [0, 0, orgWidth, orgHeight];
    if (dest_rect[0] < dest_bound[0]) dest_rect[0] = dest_bound[0];
    if (dest_rect[1] < dest_bound[1]) dest_rect[1] = dest_bound[1];
    if (dest_rect[2] > dest_bound[2]) dest_rect[2] = dest_bound[2];
    if (dest_rect[3] > dest_bound[3]) dest_rect[3] = dest_bound[3];

    // now we get real point and width/height
    dest_left = dest_rect[0];
    dest_top = dest_rect[1];
    src_left = dest_left - x_offset;
    src_top = dest_top - y_offset;
    width = dest_rect[2] - dest_rect[0];
    height = dest_rect[3] - dest_rect[1];

    return [dest_left, dest_top, width, height, src_left, src_top];
};
BmpCompositer.prototype.CompositeRgba2Rgba = function (destImgData, srcImgData, dest_left, dest_top, w, h, src_left, src_top, blend_type, clipImageData) {

    var d = BmpCompositer.prototype.GetOverlapRect(dest_left, dest_top, w, h, destImgData.width, destImgData.height, srcImgData.width, srcImgData.height, src_left, src_top);
    if (d[2] == 0 || d[3] == 0) return;
    var width = d[2];
    var height = d[3];
    dest_left = d[0];
    dest_top = d[1];
    src_left = d[4];
    src_top = d[5];
    // process row by row
    for (var row = 0; row < height; row++) {
        var dest_scan_start = (dest_top + row) * destImgData.width * 4 + dest_left * 4;
        var src_scan_start = (src_top + row) * srcImgData.width * 4 + src_left * 4;

        var blended_colors = [0, 0, 0];
        var bNonseparableBlend = blend_type >= 21;
        for (var col = 0; col < width; col++) {
            /*
            var dest_red = destImgData.data[dest_scan_start + col*4];
            var dest_green = destImgData.data[dest_scan_start + col*4+1];
            var dest_blue = destImgData.data[dest_scan_start + col*4+2]; */
            var back_alpha = destImgData.data[dest_scan_start + col * 4 + 3];

            var d_ri = dest_scan_start + col * 4;
            var d_gi = d_ri + 1;
            var d_bi = d_ri + 2;
            var d_ai = d_ri + 3;

            var s_ri = src_scan_start + col * 4;
            var s_gi = s_ri + 1;
            var s_bi = s_ri + 2;
            var s_ai = s_ri + 3;
            if (0 == back_alpha) {
                destImgData.data[d_ri] = srcImgData.data[s_ri];
                destImgData.data[d_gi] = srcImgData.data[s_gi];
                destImgData.data[d_bi] = srcImgData.data[s_bi];
                destImgData.data[d_ai] = srcImgData.data[s_ai];
                continue;
            }

            var src_alpha = srcImgData.data[s_ri + 3];
            if (src_alpha == 0) continue;


            // calculate destination alpha (it's union of source and dest alpha)
            var dest_alpha = back_alpha + src_alpha - back_alpha * src_alpha / 255;
            destImgData.data[d_ai] = dest_alpha;
            var alpha_ratio = src_alpha * 255 / dest_alpha;

            if (bNonseparableBlend) {
                var dest_scan_o = [0, 0, 0];
                dest_scan_o[0] = destImgData.data[d_ri];
                dest_scan_o[1] = destImgData.data[d_gi];
                dest_scan_o[2] = destImgData.data[d_bi];
                var src_src_o = [0, 0, 0];
                src_src_o[0] = srcImgData.data[s_ri];
                src_src_o[1] = srcImgData.data[s_gi];
                src_src_o[2] = srcImgData.data[s_bi];

                blended_colors = BmpBlender.prototype.RGBBlend(blend_mode, src_src_o, dest_scan_o);

            }

            // now we have to process color channels one by one
            for (var color = 0; color < 3; color++) {
                var dest_scan = destImgData.data[d_ri + color];
                var src_scan = srcImgData.data[s_ri + color];
                if (blend_type) {
                    var b = BmpBlender.prototype.BlendColor(blend_type, dest_scan, src_scan);
                    var blended = bNonseparableBlend ? blended_colors[color] : b;
                    blended = Math.floor((((src_scan) * (255 - (blended)) + (alpha_ratio) * (blended)) / 255));

                    destImgData.data[d_ri + color] = Math.floor((((dest_scan) * (255 - (blended)) + (alpha_ratio) * (blended)) / 255));
                } else {
                    destImgData.data[d_ri + color] = Math.floor((((dest_scan) * (255 - (src_scan)) + (alpha_ratio) * (src_scan)) / 255));
                }
            }

        }
    }
};

BmpCompositer.prototype.CompositeRgba2Rgb_Blend = function (destImgData, dest_left, dest_top, w, h, srcImageData, src_left, src_top, blend_type, clipImageData) {
    var d = BmpCompositer.prototype.GetOverlapRect(dest_left, dest_top, w, h, destImgData.width, destImgData.height, srcImageData.width, srcImageData.height, src_left, src_top);
    if (d[2] == 0 || d[3] == 0) return;
    var width = d[2];
    var height = d[3];
    dest_left = d[0];
    dest_top = d[1];
    src_left = d[4];
    src_top = d[5];
    // process row by row
    for (var row = 0; row < height; row++) {
        var dest_scan_start = (dest_top + row) * destImgData.width * 4 + dest_left * 4;
        var src_scan_start = (src_top + row) * srcImageData.width * 4 + src_left * 4;

        var blended_colors = [0, 0, 0];
        var bNonseparableBlend = blend_type >= 21;
        for (var col = 0; col < width; col++) {
            /*
            var dest_red = destImgData.data[dest_scan_start + col*4];
            var dest_green = destImgData.data[dest_scan_start + col*4+1];
            var dest_blue = destImgData.data[dest_scan_start + col*4+2]; */
            //var src_alpha = srcBmp.data[src_scan_start + col * 4 + 3];

            var d_ri = dest_scan_start + col * 4;
            var d_gi = d_ri + 1;
            var d_bi = d_ri + 2;
            var d_ai = d_ri + 3;

            var s_ri = src_scan_start + col * 4;
            var s_gi = s_ri + 1;
            var s_bi = s_ri + 2;
            var s_ai = s_ri + 3;

            var src_alpha = srcImageData.data[s_ri + 3];
            if (src_alpha == 0) continue;


            if (bNonseparableBlend) {
			    var dest_scan_o = [0,0,0];
			    dest_scan_o[0] = destImgData.data[d_ri];
			    dest_scan_o[1] = destImgData.data[d_gi];
			    dest_scan_o[2] = destImgData.data[d_bi];

			    var src_src_o = [0, 0, 0];
			    src_src_o[0] = srcImgData.data[s_ri];
			    src_src_o[1] = srcImgData.data[s_gi];
			    src_src_o[2] = srcImgData.data[s_bi];
			    blended_colors = BmpBlender.prototype.RGBBlend(blend_type, src_src_o, dest_scan_o);
		    }



            // now we have to process color channels one by one
            for (var color = 0; color < 3; color++) {
                var dest_scan = destImgData.data[d_ri + color];
                var src_scan = srcImageData.data[s_ri + color];
                var blended = bNonseparableBlend ? blended_colors[color] : BmpBlender.prototype.BlendColor(blend_type, dest_scan, src_scan);
                destImgData.data[d_ri + color] = Math.floor((((dest_scan) * (255 - (src_alpha)) + (blended) * (src_alpha)) / 255));

            }

        }
    }
};
BmpCompositer.prototype.CompositeRgba2Rgb_NoBlend = function (destImgData, dest_left, dest_top, w, h, srcImageData, src_left, src_top, clipImageData) {
    var d = BmpCompositer.prototype.GetOverlapRect(dest_left, dest_top, w, h, destImgData.width, destImgData.height, srcImageData.width, srcImageData.height, src_left, src_top);
    if (d[2] == 0 || d[3] == 0) return;
    var width = d[2];
    var height = d[3];
    dest_left = d[0];
    dest_top = d[1];
    src_left = d[4];
    src_top = d[5];
    // process row by row
    for (var row = 0; row < height; row++) {
        var dest_scan_start = (dest_top + row) * destImgData.width * 4 + dest_left * 4;
        var src_scan_start = (src_top + row) * srcImageData.width * 4 + src_left * 4;


        for (var col = 0; col < width; col++) {
            var d_ri = dest_scan_start + col * 4;
            var d_gi = d_ri + 1;
            var d_bi = d_ri + 2;
            var d_ai = d_ri + 3;

            var s_ri = src_scan_start + col * 4;
            var s_gi = s_ri + 1;
            var s_bi = s_ri + 2;
            var s_ai = s_ri + 3;

            var src_alpha = srcImageData.data[s_ri + 3];
            if (src_alpha == 255) {
                destImgData.data[d_ri] = srcImageData.data[s_ri];
                destImgData.data[d_gi] = srcImageData.data[s_gi];
                destImgData.data[d_bi] = srcImageData.data[s_bi];
            }

            if (src_alpha == 0) continue;

            // now we have to process color channels one by one
            for (var color = 0; color < 3; color++) {
                var dest_scan = destImgData.data[d_ri + color];
                var src_scan = srcImageData.data[s_ri + color];
                destImgData.data[d_ri + color] = Math.floor((((dest_scan) * (255 - (src_alpha)) + (src_scan) * (src_alpha)) / 255));

            }

        }
    }
};